package procilege.impl.expand;

import procilege.compute.ExprExpandTraveller;
import procilege.compute.PlaceType;
import procilege.compute.PlaceTypeMetadata;
import procilege.impl.NameImpl;
import procilege.lang.BoolExprOr;
import procilege.lang.CompositeExpr;
import procilege.lang.EntityExprOr;
import procilege.lang.ExprElement;
import procilege.lang.ExprSubstitute;
import procilege.lang.Name;
import procilege.lang.PrivExprComposition;
import procilege.lang.PrivExprElement;
import procilege.lang.PrivExprMergence;

@PlaceTypeMetadata(type = ExprSubstitute.class)
public class ExprSubstituteExpandPlace implements
		PlaceType<ExprExpandTraveller> {
	
	@SuppressWarnings("unchecked")
	public void visit(ExprExpandTraveller tr, Object place) {

		tr.setout(((ExprSubstitute) place).getExpr());
		Object expanded = tr.getPreviousVisitResult();
		
		ExprElement result = null;

		if (expanded instanceof CompositeExpr) {

			CompositeExpr r = null;
			if (expanded instanceof BoolExprOr)
				r = new BoolExprOr();
			else if (expanded instanceof EntityExprOr)
				r = new EntityExprOr();
			else if (expanded instanceof PrivExprComposition)
				r = new PrivExprComposition();
			else
				throw new UnsupportedOperationException("unknown: " + r);

			for (ExprElement exp : (CompositeExpr<ExprElement>) expanded) {

				ExprElement ee = substitute(tr, exp, (ExprSubstitute) place);

				r.add(ee);
			}

			result = r;
		} else if (expanded != null) {

			result = substitute(tr, (ExprElement) expanded,
					(ExprSubstitute) place);
			;
		}

		ExprExpandTraveller subtr;
		subtr = tr.spawn(new Class<?>[] { PrivExprMergence.class,
				PrivExprComposition.class });
		subtr.setout(result);
		result = (ExprElement) subtr.getPreviousVisitResult();

		tr.result(result);
	}

	private ExprElement substitute(ExprExpandTraveller tr, ExprElement elem,
			ExprSubstitute subst) {

		if (elem instanceof PrivExprMergence) {
			PrivExprMergence om = (PrivExprMergence) elem;
			PrivExprMergence nm = new PrivExprMergence(om.getBoundNames());
			nm.mergeEntity(om.getEntityExpr());
			nm.mergeRestriction(om.getRestrictionExpr());

			for (PrivExprElement ex : om.getPrivileges()) {

				PrivExprElement pv;
				pv = (PrivExprElement) substitute(tr, ex, subst);
				nm.mergePrivilege(pv);
			}
			
			tr.sayVisitAgain();

			return nm;
		} else if (elem instanceof Name) {

			NameImpl n;
			n = (NameImpl) elem;

			ExprElement result;

			if (n.equals(subst.getBoundedName())) {
				result = subst.getObject();

				tr.setout(result);
				result = (ExprElement) tr.getPreviousVisitResult();

//				tr.reportBoundName(n);

				return result;
			}

			ExprElement subject = elem;
			tr.setout(subject);
			subject = (ExprElement) tr.getPreviousVisitResult();

			result = new ExprSubstitute(subject, subst.getObject(), subst
					.getBoundedName());

			return result;
		}

		ExprSubstitute result;
		result = new ExprSubstitute(elem, subst.getObject(), subst
				.getBoundedName());

		return result;
	}
}